package com.seed.core.builder.stat;

import cn.hutool.core.util.StrUtil;
import com.mybatisflex.core.query.QueryTable;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.query.RawQueryColumn;
import com.seed.core.builder.datapermis.DataPermisWrapper;
import com.seed.core.builder.declare.DeclaredBuilder;
import com.seed.core.builder.declare.QueryDeclaredBuilder;
import com.seed.core.builder.keyword.KeywordWrapper;
import com.seed.core.builder.pojo.Join1;
import com.seed.core.builder.pojo.OnCondition1;
import com.seed.core.builder.pojo.Table1;
import com.seed.core.builder.pojo.WhereCondition1;
import com.seed.core.builder.query.InBuilder;
import com.seed.core.builder.search.SearchedWrapper;
import com.seed.core.column.Column;
import com.seed.core.enumeration.PropertyType;
import com.seed.core.enumeration.StatType;
import com.seed.core.pojo.seed.*;
import com.seed.core.query.pojo.Declared;
import com.seed.util.ConditionKit;
import com.seed.util.SeedKit;
import com.seed.util.StrKit;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class StatBuilder {
    private final Seed seed;
    private final Stat stat;

    private SearchedWrapper searchedWrapper;
    private KeywordWrapper keywordWrapper;
    private DataPermisWrapper dataPermisWrapper;

    private final Map<String, Join1> joins = new LinkedHashMap<>();

    public StatBuilder(Seed seed, Stat stat) {
        this.seed = seed;
        this.stat = stat;
    }

    public QueryWrapper build() {
        QueryTable table = new QueryTable(seed.getTableName()).as("t");
        QueryWrapper qw = QueryWrapper.create().from(table);

        StatType statType = stat.getType();
        String declare = stat.getDeclare();
        Map<String, Declared> declareds = new QueryDeclaredBuilder(seed, List.of(declare)).build();
        Map.Entry<String, Declared> first = declareds.entrySet().iterator().next();
        Declared declared = first.getValue();
        switch (declared.getType()) {
            case none, asterisk -> {
                if(!statType.equals(StatType.count)) throw new RuntimeException("只有【count】可以用【*】");
                qw.select("count(*)");
            }
            case column -> {
                Column column = (Column) declared.getProperty();
                qw.select(String.format("%s(`t`.`%s`)", statType, column.getName()));
            }
            default -> {
                qw.select(String.format("`%s`.`id`", inSelectAlias(null, declareds)));
                initJoins(null, declareds);
            }
        }

        Map<String, Join1> joinList = new LinkedHashMap<>();
        for(Map.Entry<String, Join1> entry: joins.entrySet()) {
            joinList.putIfAbsent(entry.getKey(), entry.getValue());
        }
        if(searchedWrapper != null) {
            for(Map.Entry<String, Join1> entry: searchedWrapper.getJoins().entrySet()) {
                joinList.putIfAbsent(entry.getKey(), entry.getValue());
            }
        }
        if(keywordWrapper != null) {
            for(Map.Entry<String, Join1> entry: keywordWrapper.getJoins().entrySet()) {
                joinList.putIfAbsent(entry.getKey(), entry.getValue());
            }
        }
        if(dataPermisWrapper != null) {
            for(Map.Entry<String, Join1> entry: dataPermisWrapper.getJoins().entrySet()) {
                joinList.putIfAbsent(entry.getKey(), entry.getValue());
            }
        }

        for(Join1 join: joinList.values()) {
            Table1 table1 = join.getTable();
            QueryTable queryTable = new QueryTable(table1.getName(), table1.getAlias());
            switch (join.getJoinType()) {
                case inner -> qw.innerJoin(queryTable).on(ConditionKit.build(join.getOn()));
                case left -> qw.leftJoin(queryTable).on(ConditionKit.build(join.getOn()));
                case right -> qw.rightJoin(queryTable).on(ConditionKit.build(join.getOn()));
            }
        }

        Map<String, InBuilder> inBuilders = new LinkedHashMap<>();
        if(searchedWrapper != null) inBuilders.putAll(searchedWrapper.getInBuilders());

        if(keywordWrapper != null) {
            for(Map.Entry<String, InBuilder> entry: keywordWrapper.getInBuilders().entrySet()) {
                String key = entry.getKey();
                InBuilder value = entry.getValue();
                InBuilder inBuilder = inBuilders.get(key);
                if(inBuilder == null) {
                    inBuilders.putIfAbsent(key, value);
                } else {
                    inBuilder.setDeclared(value.getDeclared());
                }
            }
        }

        for(Map.Entry<String, InBuilder> entry: inBuilders.entrySet()) {
            qw.and(entry.getValue().getQueryCondition());
        }

        if(searchedWrapper != null) {
            for(WhereCondition1 condition: searchedWrapper.getWheres()) qw.and(ConditionKit.build(condition));
        }

        if(keywordWrapper != null) {
            qw.and(queryWrapper -> {
                for(WhereCondition1 condition: keywordWrapper.getWheres()) queryWrapper.or(ConditionKit.build(condition));
            });
        }

//        if(seedDataPermied != null) qw.and(seedDataPermied.getQueryCondition());

        return switch (declared.getType()) {
            case none, asterisk, column -> qw;
            default -> QueryWrapper.create()
                    .select(String.format("%s(`%s`)", statType, statisColumnName(declareds)))
                    .from(statisTableName(seed, declareds))
                    .where(new RawQueryColumn("id").in(qw));
        };
    }

    private String inSelectAlias(String basePath, Map<String, Declared> declareds) {
        Map.Entry<String, Declared> first = declareds.entrySet().iterator().next();
        Declared declared = first.getValue();
        return switch (declared.getType()) {
            case none, asterisk, column -> basePath;
            default -> inSelectAlias(StrKit.concat(basePath, first.getKey()), declared);
        };
    }

    private void initJoins(String basePath, Map<String, Declared> declareds) {
        Map.Entry<String, Declared> first = declareds.entrySet().iterator().next();
        String cross = first.getKey();
        Declared declared = first.getValue();

        String selfTableAlias = StrKit.selfTableAlias(basePath);
        String finalPath = StrKit.concat(basePath, cross);
        Property property = declared.getProperty();
        if(!(property instanceof Relation relation)) return;

        Seed targetSeed = relation.targetSeed();
        String targetTableAlias = StrUtil.toCamelCase(finalPath);
//        QueryTable targetTable = new QueryTable(targetSeed.getTableName()).as(targetTableAlias);
        Table1 targetTable = new Table1(targetSeed.getTableName()).as(targetTableAlias);

        PropertyType propertyType = declared.getType();
        switch (propertyType) {
            case hasOne, belongsTo, hasMany -> {
//                QueryCondition on = relation.getQueryCondition(basePath, cross);
                OnCondition1 on = getQueryCondition1(relation, basePath, cross);
                joins.put(finalPath, new Join1(targetTable, on));
                initJoins(finalPath, declared);
            }
            case habtm -> {
//                Habtms habtm = (Habtms) relation;
//                String throughAlias = StrKit.throughAlias(basePath, cross);
//                QueryTable throughTable = new QueryTable(habtm.throughTableName()).as(throughAlias);
//                QueryCondition throughOn = habtm.getThroughQueryCondition(selfTableAlias, throughAlias);
//                joins.put(throughAlias, new Join(throughTable, throughOn, JoinType.inner));
//
//                QueryCondition on = habtm.getQueryCondition(selfTableAlias, throughAlias, targetTableAlias);
//                joins.put(finalPath, new Join(targetTable, on));
//                initJoins(finalPath, declared);
            }
        }
    }

    private OnCondition1 getQueryCondition1(Relation relation, String selfTableAlias, String targetTableAlias) {
        Table1 selfTable = new Table1(relation.selfSeed().getTableName()).as(selfTableAlias);
        Table1 targetTable = new Table1(relation.targetSeed().getTableName()).as(targetTableAlias);
        String inferSelfColumn = relation.inferSelfColumn();
        String inferTargetColumn = relation.inferTargetColumn();

        OnCondition1 on = new OnCondition1(selfTable, inferSelfColumn, targetTable, inferTargetColumn);
        on.setExtra(SeedKit.formatExtra(relation.getExtraSeed(), relation.getExtra()));

        return on;
    }

    private String statisTableName(Seed seed, Map<String, Declared> declareds) {
        Map.Entry<String, Declared> first = declareds.entrySet().iterator().next();
        Declared declared = first.getValue();
        return switch (declared.getType()) {
            case none, asterisk, column -> seed.getTableName();
            default -> {
                Relation relation = (Relation) declared.getProperty();
                yield statisTableName(relation.targetSeed(), declared);
            }
        };
    }

    private String statisColumnName(Map<String, Declared> declareds) {
        Map.Entry<String, Declared> first = declareds.entrySet().iterator().next();
        Declared declared = first.getValue();
        return switch (declared.getType()) {
            case none, asterisk -> "id";
            case column -> ((Column) declared.getProperty()).getName();
            default -> statisColumnName(declared);
        };
    }

    public Stat getStat() {
        return stat;
    }

    public StatBuilder setSearchedWrapper(SearchedWrapper searchedWrapper) {
        this.searchedWrapper = searchedWrapper;
        return this;
    }

    public StatBuilder setKeywordWrapper(KeywordWrapper keywordWrapper) {
        this.keywordWrapper = keywordWrapper;
        return this;
    }

    public StatBuilder setDataPermisWrapper(DataPermisWrapper dataPermisWrapper) {
        this.dataPermisWrapper = dataPermisWrapper;
        return this;
    }
}
